﻿using System;
using System.Collections.Generic;
using System.Linq;
using Accountant.DataModel;
using Accountant.Primitives;

namespace Accountant.Utils
{
  public static class Ext
  {
    public static Money Sum(this IEnumerable<Money> source)
    {
      var list = source.ToList();
      if (list.Count == 0) throw new Exception("Empty sequence");
      if (list.Count == 1) return list[0];
      if (list.Select(i => i.Currency).Distinct().Count() != 1)
        throw new Exception("Cannot sum different currencies");
      return new Money(list[0].Currency, list.Sum(i => i.Amount));
    }
    public static void AddRange<T>(this ICollection<T> dest, IEnumerable<T> src)
    {
      foreach (var item in src)
        dest.Add(item);
    }
    public static void AddRange<T>(this ICollection<T> dest, params T[] src)
    {
      foreach (var item in src)
        dest.Add(item);
    }
    public static Account Root(this Account acc)
    {
      try
      {
        while (acc.BaseAccount != null && !acc.BaseAccount.IsSpecial(KnownAccounts.All))
          acc = acc.BaseAccount;
        return acc;
      }
      catch (Exception)
      {
        throw new Exception("Root() requires that all base " +
                            "accounts are into the context!");
      }
    }

    public static bool IsSpecial(this Account acc, KnownAccounts known)
    {
      return acc == acc.Context.Accounts[(int)known];
    }
    public static bool Is(this Account acc, Account other)
    {
      try
      {
        while (acc != other && acc.BaseAccount != null)
          acc = acc.BaseAccount;
        return acc == other;
      }
      catch (Exception)
      {
        throw new Exception("Is() requires that all base " +
                            "accounts are into the context!");
      }
    }
    public static bool IsIncome(this Account acc)
    {
      return acc.Root().IsSpecial(KnownAccounts.Income);
    }
    public static bool IsExpense(this Account acc)
    {
      return acc.Root().IsSpecial(KnownAccounts.Expense);
    }
    public static bool IsAsset(this Account acc)
    {
      return acc.Root().IsSpecial(KnownAccounts.Asset);
    }
    public static bool IsExchange(this Account acc)
    {
      return acc.Root().IsSpecial(KnownAccounts.Exchange);
    }
    public static bool IsLiability(this Account acc)
    {
      return acc.Root().IsSpecial(KnownAccounts.Liability);
    }

    public static bool Is(this Transaction t, Account other)
    {
      return t.Credit.Is(other) || t.Debit.Is(other);
    }
    public static bool IsIncome(this Transaction t)
    {
      return t.Debit.IsIncome();
    }
    public static bool IsTransfer(this Transaction t)
    {
      return t.Credit.IsAsset() && t.Debit.IsAsset();
    }
    public static bool IsExpense(this Transaction t)
    {
      return t.Credit.IsExpense();
    }
    public static bool IsAsset(this Transaction t)
    {
      return t.Credit.IsAsset() || t.Debit.IsAsset();
    }
    public static bool IsExchange(this Transaction t)
    {
      return t.Credit.IsExchange() || t.Debit.IsExchange();
    }
    public static bool IsLiability(this Transaction t)
    {
      return t.Credit.IsLiability() || t.Debit.IsLiability();
    }
    public static bool IsRevisioned(this Transaction t)
    {
      return (!t.Credit.IsAsset() || t.Credit.LastRevisionDate >= t.Timestamp) &&
             (!t.Debit.IsAsset() || t.Debit.LastRevisionDate >= t.Timestamp);
    }
    public static IEnumerable<Transaction> Incomes(this IEnumerable<Transaction> transactions)
    {
      return transactions.Where(t => t.IsIncome());
    }
    public static IEnumerable<Transaction> Expenses(this IEnumerable<Transaction> transactions)
    {
      return transactions.Where(t => t.IsExpense());
    }
    public static IEnumerable<Transaction> Assets(this IEnumerable<Transaction> transactions)
    {
      return transactions.Where(t => t.IsAsset());
    }
    public static IEnumerable<Transaction> Exchanges(this IEnumerable<Transaction> transactions)
    {
      return transactions.Where(t => t.IsExchange());
    }

    public static MoneyBag Credits(this IEnumerable<Transaction> transactions, Func<Account, bool> predicate = null)
    {
      predicate = predicate ?? (_ => true);
      return new MoneyBag(transactions
        .Where(t => predicate(t.Credit))
        .GroupBy(t => t.Currency)
        .Select(g => new Money(g.Key, g.Sum(t => t.Amount)))
        .Where(m => m.Amount != 0));
    }
    public static MoneyBag Debits(this IEnumerable<Transaction> transactions, Func<Account, bool> predicate = null)
    {
      predicate = predicate ?? (_ => true);
      return new MoneyBag(transactions
        .Where(t => predicate(t.Debit))
        .GroupBy(t => t.Currency)
        .Select(g => new Money(g.Key, g.Sum(t => t.Amount)))
        .Where(m => m.Amount != 0));
    }
    public static MoneyBag Balance(this IEnumerable<Transaction> transactions, Func<Account, bool> predicate = null)
    {
      return transactions.Credits(predicate) - transactions.Debits(predicate);
    }

    public static IEnumerable<GroupTransaction> GroupTransactions(this IEnumerable<Transaction> transactions)
    {
      return transactions.GroupBy(t => t.Timestamp)
        .Select(g => GroupTransaction.Create(g.ToList()))
        .Where(g => g != null);
    }
  }
}